/*
 *   Copyright 2008 Luca Bellonda <lbellonda@gmail.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as
 *   published by the Free Software Foundation; either version 2 or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include <QtDBus/QtDBus>
#include <QDBusConnection>

#include "scanthread.h"
//#define   _SPEED_DEBUG

#ifdef DUMP
#define   D(x)    x
#else
#define   D(x)
#endif

ScanThread::ScanThread(QObject *parent) : QThread(parent)
{
    m_isAbort = false;
    m_config = new KConfig("dbusappletrc");
    m_resultSession = NULL ;
    m_resultSystem = NULL ;
}

ScanThread::~ScanThread()
{
    m_mutex.lock();
    m_isAbort = true;
    m_mutex.unlock();
    wait();
}


void ScanThread::run()
{
    D(printf("start thread\n"));
    DBusAppletItem* resultSession = new DBusAppletItem() ;
    DBusAppletItem* resultSystem = new DBusAppletItem() ;
    execute(resultSession, resultSystem);
    m_mutex.lock();
    if (NULL != m_resultSession)
        delete m_resultSession ;
    if (NULL != m_resultSystem)
        delete m_resultSystem ;
    m_resultSession = resultSession;
    m_resultSystem = resultSystem;
    m_mutex.unlock();
    D(printf("emitting \n"));
    emit scanEnd();
    D(printf("fine thread\n"));
}


void ScanThread::getData(QVariant &system, QVariant &session)
{
    m_mutex.lock();
    DBusAppletItem& resultSession = *m_resultSession;
    DBusAppletItem& resultSystem = *m_resultSystem ;
    system.setValue(resultSession);
    session.setValue(resultSystem);
    m_mutex.unlock();
}

/*
void ScanThread::addToResult(QStringList &result, const int id, const int idParent, const QString &type, const QString &value)
{
    D(printf(" ADDING %s\n", QString("%1|%2|%3|%4").arg(id).arg(idParent).arg(type).arg(value)).toAscii().data());
    result.append(QString("%1|%2|%3|%4").arg(id).arg(idParent).arg(type).arg(value));
}
*/

QString ScanThread::scanForArguments(const QString &name, QDomNode &node)
{
    QString method = name;
    bool isFirst = true ;
    D(printf("( "));
    method.append("( ");
    QDomElement element = node.firstChildElement();
    while (!element.isNull()) {
        if (element.tagName() == QLatin1String("arg")) {
            QString direction = element.attribute("direction");
            QString type = element.attribute("type");
            D(printf(" %s%s[%s]",
                     (isFirst ? "" : ","),
                     type.toAscii().data(),
                     direction.toAscii().data()));
            method.append(QString(" %1%2[%3]").arg(isFirst ? "" : ",").arg(type).arg(direction)) ;
            isFirst = false;
        }
        element = element.nextSiblingElement();
    }//for
    D(printf(")\n"));
    method.append(")");
    return method ;
}//scanForArguments()

void ScanThread::scanForMethods(QDomNode &node, int &idObject, const int idParent, DBusAppletItem *result)
{
    QDomElement element = node.firstChildElement();
    while (!element.isNull()) {
        idObject ++ ;
        QString name = element.attribute(QLatin1String("name"));
        if (element.tagName() == QLatin1String("method")) {
            D(printf("  METODO: %s", name.toAscii().data()));
            result->append(idObject, idParent, "METHOD", scanForArguments(name, element));
        } else if (element.tagName() == QLatin1String("signal")) {
            D(printf("  SEGNALE: %s\n", name.toAscii().data()));
            result->append(idObject, idParent, "SIGNAL", name);
        } else if (element.tagName() == QLatin1String("property")) {
            D(printf("  PROPRIETA: %s\n", name.toAscii().data()));
            result->append(idObject, idParent, "PROPERTY", name);
        } else {
            D(printf("  UNKNOWN***: %s\n", name.toAscii().data()));
            result->append(idObject, idParent, "UNKNOWN", name);
        }
        element = element.nextSiblingElement();
    }
}

void ScanThread::scan(const QString &service, QDomNode &doc, QDBusConnection &connection, int &idObject, const int idParent, DBusAppletItem *result)
{
    int nodi = doc.childNodes().count();
    for (int i = 0 ; i < nodi ; i ++) {
        QDomNode childNode = doc.childNodes().item(i) ;
        if (childNode.isElement()) {
            DBusAppletItem *last;
            //printf("trovato testo %s\n", childNode.nodeValue().toAscii().data());
            idObject ++;
            const int idOParent = idObject ;
            QDomElement element = childNode.toElement();
            QString name = element.attribute(QLatin1String("name"));
            if (element.tagName() == QLatin1String("node")) {
                D(printf("PATH: %s\n", name.toAscii().data()));
                last = result->append(idObject, idParent, "PATH", name);
                introspect(service, "/" + name, connection, idObject, idOParent, last);
                scanForMethods(element, idObject, idParent, result->last());
            } else if (element.tagName() == QLatin1String("interface")) {
                D(printf("INTERFACE: %s\n", name.toAscii().data()));
                last = result->append(idObject, idParent, "INTERFACE", name);
                scanForMethods(element, idObject, idOParent, last);
            }
        } //if
    }//for
    return ;
}//scan()

void ScanThread::introspect(const QString &service, const QString &path, QDBusConnection &connection, int &idObject, const int idParent, DBusAppletItem *result)
{
    QDomDocument doc;
    D(printf("...........................introspecting %s for %s\n", service.toAscii().data(), path.toAscii().data()));
    QDBusInterface iface(service, path, "org.freedesktop.DBus.Introspectable", connection);
    if (!iface.isValid()) {
        D(printf("..............................no valid \n"));
        return ;
    }

    QDBusReply<QString> xml = iface.call("Introspect");
    D(printf("...........................replied\n"));
    if (!xml.isValid()) {
        D(printf("...........................no valid 2\n"));
        return ;
    }
    doc.setContent(xml);
    QDomElement node = doc.documentElement();
    scan(service, node, connection, idObject, idParent, result);
    D(printf("**   XTOTALE: %d\n", result->total()));
}

void ScanThread::setDBusData(const QString& name, const QStringList &services, QDBusConnection &connection, DBusAppletItem *result)
{
    KConfigGroup cfgGroup ;
    m_mutex.lock();
    if (NULL != m_config) {
        m_config->markAsClean();
        m_config->reparseConfiguration();
    }
    m_mutex.unlock();

    if (NULL != m_config) {
        cfgGroup = m_config->group(name);
    }

    QStringList exclusionList ;
    QHash<QString, QString>exclusionMap;
    exclusionList = cfgGroup.readEntry("exclusionListServices", QStringList());
    //cfgGroup.writeEntry( "Account", "tryToWriteSomething" );
    //m_config->sync();
    QStringListIterator exclusionIterator(exclusionList);
    while (exclusionIterator.hasNext()) {
        QString excl = exclusionIterator.next();
        exclusionMap[excl] = excl;
    }
    int id = 0;
    QStringListIterator serviceIterator(services);
    while (serviceIterator.hasNext()) {
        QString service = serviceIterator.next();
        if (!exclusionMap.contains(service)) {
#ifdef   _SPEED_DEBUG
            if (service.startsWith("org.kde.kded")) {
#endif
                id ++;
                DBusAppletItem *item = result->append(id, 0, "SERVICE", service) ;
                D(printf("****  %d %s\n", id, service.toAscii().data()));
                bool   isAbort = false ;
                const int idParent = id ;
                introspect(service, QString("/"), connection, id, idParent, item);
                m_mutex.lock();
                isAbort = m_isAbort;
                m_mutex.unlock();
                if (isAbort)
                    break;
#ifdef   _SPEED_DEBUG
            }
#endif
        }
    }
    D(printf("**TOTALE: %d\n", result->total()));
    D(printf("finito servizio\n"));
}

void ScanThread::execute(DBusAppletItem *resultSession, DBusAppletItem *resultSystem)
{
    QDBusConnection m_connectionSystem = QDBusConnection::systemBus();
    const QStringList systemServiceNames = m_connectionSystem.interface()->registeredServiceNames();
    setDBusData(SOURCE_SYSTEM_SERVICES, systemServiceNames, m_connectionSystem, resultSystem);
    D(printf("finito sistema\n"));
    QDBusConnection m_connectionSession = QDBusConnection::sessionBus();
    const QStringList sessionServiceNames = m_connectionSession.interface()->registeredServiceNames();
    setDBusData(SOURCE_SESSION_SERVICES, sessionServiceNames, m_connectionSession, resultSession);
    D(printf("finito sessione\n"));
}

void ScanThread::scan()
{
    if (!isRunning()) {
        start(LowPriority);
    }
}
